
/*
XMail OSAX -- Copyright (C) 2004, 2005, 2006, 2007 Jean-Baptiste LE STANG osax@lestang.org

	
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "auth-cram-md5.h"
#include "smtp.h" 
#include "algorithm.h" 
#include "inetsocket.h" 
#include "smtp-string.h"

void auth_cram_md5_init ()    { /* do nothing */ }

/**
 * auth_cram_md5_cleanup() will be called once when the plugin is 
 * unloaded.
 */
void auth_cram_md5_cleanup () { /* do nothing */ }




/**
 * auth_cram_md5_response() calculates the base64 encoded client response
 * to the server challenge in msg. The returned string is 
 * malloc()ed and must be freed using free(). The length of the
 * returned string is stored in siz.
 *
 * @param srv  The smtp_srv_t object.
 * @param msg  The response of the SMTP server to the 
 *             AUTH CRAM-MD5 command.
 * @param siz  The length of the returned string will be 
 *             stored in siz.
 *
 * @returns A base64 encoded response calculated on base of the
 * challenge received from the SMTP server.
 */
char * auth_cram_md5_response (smtp_srv_t * srv, char * msg, int *siz)
{
    char *buf, *enc_buf = NULL;
    unsigned char dig[16], hex_dig[33];
    int usr_len, size;

    usr_len = strlen (srv->username);

    /* Challenge */
    str_trim (msg, " ");
    buf = (char*)malloc (strlen (msg));

    if (strlen (msg) <= 4){
		smtp_set_error(srv,"Invalid CRAM-MD5 challenge received.",-1);
        free(buf);
        return (NULL);
    }
	printf("String to decode64 is %s\n",msg+4);
    size = alg_b64_decode (msg+4, strlen (msg+4), buf);
    
    alg_hmac ((unsigned char *)buf, size,(unsigned char *)srv->password, strlen (srv->password), dig,H_MD5);
    free(buf);

    str_hexify ((char *)hex_dig, (char *)dig, 16);

    /* Response: BASE64("USERNAME HEX_DIGEST") */
    buf = (char*)malloc (usr_len + 34);
    sprintf (buf, "%s %.32s", srv->username, hex_dig);

    /* BASE64 encoding */
    enc_buf = (char*)malloc (2 * (usr_len + 33) + 4);
    *siz = alg_b64_encode (buf, usr_len + 33, enc_buf);

    free(buf);
    return (enc_buf);
}

/*
	http://www.armware.dk/RFC/rfc/rfc2195.html
*/

void test_auth_cram_md5_response ()
{
	char * buf = "PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+";
	char  * buffer = NULL;
	char * password = "tanstaaftanstaaf";
	unsigned char dig[16], hex_dig[33];
    int size;
	char * finaloutput = NULL;
	char * finaloutputenc = NULL;
	int use_len = strlen("tim");
	int input_len = strlen(buf);
	buffer = (char *) malloc(96*sizeof(char));
	memset(buffer,0,96);
	size = alg_b64_decode (buf,input_len, buffer);
    /*
	fprintf(stdout,"Input buffer is %s od  isze %d\n",buf,strlen(buf));
	fprintf(stdout,"Decoded buffer is %s of size %d\n",buffer,size);
     */
	alg_hmac ((unsigned char *)buffer, strlen(buffer), (unsigned char *)password, strlen (password), dig, H_MD5);
    str_hexify ((char *)hex_dig, (char *)dig, 16);
    finaloutput = (char*)malloc (use_len + 34);
    sprintf (finaloutput, "%s %.32s", "tim", hex_dig);
	finaloutputenc = (char*)malloc (2 * (use_len + 33) + 4);
    alg_b64_encode (finaloutput, use_len + 33, finaloutputenc);
}


/**
 * auth_cram_md5_authenticate() is called by libsmtp after a positive
 * response to the EHLO command was received. All stuff
 * needed for authentication should be implemented here.
 * If auth_cram_md5_authenticate() fails for some reason, -1 must be 
 * returned and an appropriate error message must be set with
 * the function err_set_error(). On success 0 is expected to 
 * be returned.
 *
 * @param srv   The smtp_srv_t object.
 *
 * @returns 0 on success, otherwise -1.
 */
int auth_cram_md5_authenticate (smtp_srv_t * srv)
{
    char *rsp, *enc;
    int siz;

    /* AUTH CRAM-MD5 */
    if (smtp_write (srv, "AUTH CRAM-MD5\r\n", 15) == -1){
        return (-1); /* error msg is already set */
    }

    /* Challenge */
    if ((rsp = smtp_read_response_ (srv, 1)) == NULL){
        return (-1); /* error msg is already set */
    }

    /* Response */
    if ((enc = auth_cram_md5_response (srv, rsp, &siz)) == NULL){
        free(rsp);
        return (-1); /* error msg is already set */
    }
    free(rsp);
            
    if (smtp_write (srv, enc, siz) == -1){
        free(enc);
        return (-1); /* error msg is already set */
    }

    /* Authentication successfull ? */
    if ((rsp = smtp_read_response_ (srv, 1)) == NULL){
        free(enc);
        return (-1); /* error msg is already set */
    }

    free(rsp);free(enc);
    return (0);
}

/**
 * auth_cram_md5_read() will be called by libsmtp anytime it reads
 * from the underlying socket. If the SASL mechanism 
 * implements some sort of decryption, this is the place
 * for it.
 * If there is no need for decryption/modifying of the data
 * block, ssl_read() must be called using srv->sck, buf and 
 * bufsiz as parameter values.
 * If auth_cram_md5_read() fails for some reason, -1 must be returned 
 * and an appropriate error message must be set with
 * the function err_set_error(). On success the number of
 * bytes that were actually stored in buf must be returned.
 *
 * @param srv     The smtp_srv_t object.
 * @param buf     The buffer that is to be filled.
 * @param bufsiz  The number of bytes that should be read at most.
 *
 * @returns The number of bytes that were stored in buf or -1 
 * if an error occured.
 */
int auth_cram_md5_read (smtp_srv_t * srv, const char * buf, unsigned int bufsiz)
{
    return (ssl_read (&srv->sck, buf, bufsiz));
}

/**
 * auth_cram_md5_write() will be called by libsmtp anytime it writes
 * to the underlying socket. If the SASL mechanism 
 * implements some sort of encryption, this is the place
 * for it.
 * If there is no need for encryption/modifying of the data
 * block, ssl_write() must be called using srv->sck, buf and 
 * bufsiz as parameter values.
 * If auth_cram_md5_write() fails for some reason, -1 must be returned 
 * and an appropriate error message must be set with
 * the function err_set_error(). On success the number of
 * bytes that were actually written must be returned.
 *
 * @param srv     The smtp_srv_t object.
 * @param buf     The buffer that is to be filled.
 * @param bufsiz  The number of bytes that should be read at most.
 *
 * @returns The number of bytes actually written or -1 if an error
 * occured.
 */
int auth_cram_md5_write (smtp_srv_t * srv, const char * buf, unsigned int bufsiz)
{
    return (ssl_write (&srv->sck, buf, bufsiz));
}

